/*
 *  linux/kernel/traps.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * 'Traps.c' handles hardware traps and faults after we have saved some
 * state in 'asm.s'. Currently mostly a debugging-aid, will be extended
 * to mainly kill the offending process (probably by giving it a signal,
 * but possibly by killing it outright if necessary).
 */
#include <string.h> 

#include <linux/head.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <asm/io.h>

// 调用get_seg_byte时，当前是在内核状态，DS、ES这些被改成了内核的数据段0x10
// 而现在要访问的是用户的数据段，不能直接使用DS、ES来访问，因为段不同
// 因此，使用另外的FS寄存器来加载这个段进行访问
// =a" (__res) -- 从al中取出结构，作为返回值
// "0" (seg) - seg传入给ax，后面用于mov ax, fs，给到fs
// "m" (*(addr)) -- 将add作为内存操作数。movb %%fs:%2,%%al这里即从seg:addr中取值给到al
#define get_seg_byte(seg,addr) ({ \
register char __res; \
__asm__ __volatile__("push %%fs;mov %%ax,%%fs;movb %%fs:%2,%%al;pop %%fs" \
	:"=a" (__res):"0" (seg),"m" (*(addr))); \
__res;})

// 同上，只不过操作数长度不太一样
#define get_seg_long(seg,addr) ({ \
register unsigned long __res; \
__asm__ __volatile__("push %%fs;mov %%ax,%%fs;movl %%fs:%2,%%eax;pop %%fs" \
	:"=a" (__res):"0" (seg),"m" (*(addr))); \
__res;})

// 取fs寄存器
#define _fs() ({ \
register unsigned short __res; \
__asm__ __volatile__("mov %%fs,%%ax":"=a" (__res):); \
__res;})

int do_exit(long code);

void page_exception(void);

void divide_error(void);
void debug(void);
void nmi(void);
void int3(void);
void overflow(void);
void bounds(void);
void invalid_op(void);
void device_not_available(void);
void double_fault(void);
void coprocessor_segment_overrun(void);
void invalid_TSS(void);
void segment_not_present(void);
void stack_segment(void);
void general_protection(void);
void page_fault(void);
void coprocessor_error(void);
void reserved(void);
void parallel_interrupt(void);
void irq13(void);

// 统一的异常处理函数
static void die(char * str,long esp_ptr,long nr)
{
	long * esp = (long *) esp_ptr;
	int i;

	// 打印名称、错误码（16位有效）
	printk("%s: %04x\n\r",str,nr&0xffff);

	// 部分寄存器的值，这是中断和异常发生时，硬件自动压入的CPL=3时的值,主要是栈、触发的地址、EFLAGS
	printk("EIP:\t%04x:%p\nEFLAGS:\t%p\nESP:\t%04x:%p\n", esp[1],esp[0],esp[2],esp[4],esp[3]);
	printk("fs: %04x\n",_fs());

	// 当前所用的LDT情况：代码段？数据段基地址，限长。TODO：ldt[1]是代码段?
	printk("base: %p, limit: %p\n",get_base(current->ldt[1]),get_limit(0x17));	// 0x17 -- SS & DS

	// esp[4] = ss，看下中断之前用的栈是不是用户栈。是就打印4字节栈的内容
	// 也有可能是用户在执行系统调用时时触发的异常，所以这里判断一下
	if (esp[4] == 0x17) {
		printk("Stack: ");
		// 栈里面的内容
		for (i=0;i<4;i++)
			// 下面是以ss:esp为地址，取4字节用户栈的内容
			printk("%p ",get_seg_long(0x17,i+(long *)esp[3]));	// 取用户栈段0x17中的数据，esp[3]取内核栈中的esp
		printk("\n");
	}

	str(i);		// 取得任务号，保存到i中
	printk("Pid: %d, process nr: %d\n\r",current->pid,0xffff & i);
	// esp[1]->cs, esp[0]->eip，即打印10个字节触发异常的机器码
	for(i=0;i<10;i++)
		printk("%02x ",0xff & get_seg_byte(esp[1],(i+(char *)esp[0])));  // esp[1]用户代码段，esp[0]是原来的eip
	printk("\n\r");
	do_exit(11);		/* play segment exception */
}

// 以下是相应异常的C处理函数
void do_double_fault(long esp, long error_code)
{
	die("double fault",esp,error_code);
}

void do_general_protection(long esp, long error_code)
{
	die("general protection",esp,error_code);
}

// 除0异常，打印信息显示错误码
void do_divide_error(long esp, long error_code)
{
	die("divide error",esp,error_code);
}

// 这里和其它的处理函数不同。多了一些参数，可能是方便直接使用吧？s
// 结合压栈中内容的结构图，可以看到error_code上面的栈值为这些寄存器
// 所以他按照相应的顺序，填入这些参数，就可以依次获取这些参数了。
void do_int3(long * esp, long error_code,
		long fs,long es,long ds,
		long ebp,long esi,long edi,
		long edx,long ecx,long ebx,long eax)
{
	int tr;

	// 断点指令，没有做什么处理，只是打印一下断点。然后后面应当继续执行后续的代码。
	// 断点的作用只是用来暂停CPU的运行
	__asm__ __volatile__("str %%ax":"=a" (tr):"0" (0));
	printk("eax\t\tebx\t\tecx\t\tedx\n\r%8x\t%8x\t%8x\t%8x\n\r",
		eax,ebx,ecx,edx);
	printk("esi\t\tedi\t\tebp\t\tesp\n\r%8x\t%8x\t%8x\t%8x\n\r",
		esi,edi,ebp,(long) esp);
	printk("\n\rds\tes\tfs\ttr\n\r%4x\t%4x\t%4x\t%4x\n\r",
		ds,es,fs,tr);
	printk("EIP: %8x   CS: %4x  EFLAGS: %8x\n\r",esp[0],esp[1],esp[2]);
}

void do_nmi(long esp, long error_code)
{
	die("nmi",esp,error_code);
}

void do_debug(long esp, long error_code)
{
	die("debug",esp,error_code);
}

void do_overflow(long esp, long error_code)
{
	die("overflow",esp,error_code);
}

void do_bounds(long esp, long error_code)
{
	die("bounds",esp,error_code);
}

void do_invalid_op(long esp, long error_code)
{
	die("invalid operand",esp,error_code);
}

void do_device_not_available(long esp, long error_code)
{
	die("device not available",esp,error_code);
}

void do_coprocessor_segment_overrun(long esp, long error_code)
{
	die("coprocessor segment overrun",esp,error_code);
}

void do_invalid_TSS(long esp,long error_code)
{
	die("invalid TSS",esp,error_code);
}

void do_segment_not_present(long esp,long error_code)
{
	die("segment not present",esp,error_code);
}

void do_stack_segment(long esp,long error_code)
{
	die("stack segment",esp,error_code);
}

void do_coprocessor_error(long esp, long error_code)
{
	if (last_task_used_math != current)
		return;
	die("coprocessor error",esp,error_code);
}

void do_reserved(long esp, long error_code)
{
	die("reserved (15,17-47) error",esp,error_code);
}

// 中断及异常初始化程序
void trap_init(void)
{
	int i;

	// idt表在head.s中定义，其中已经进行了初始化
	// 设置idt陷阱门描述符，分别设置各项中断及异常处理向量
	// 与中断门相比，陷阱门允许中断嵌套，即进入处理函数后，EFLAGS.IF标志位不影响
	// 而中断门会在进入处理函数时自动清0 IF标志，即禁用中断。然后在退出中断时，再IF=1，开启中断响应

	// 这些处理函数在汇编中定义
	// 设置的idt表中相应序号的表项，这个表在head.s中定义
	// 注意这里是设置DPL=0，这样不允许用户使用INT指令来触发执行
	// set_trap_gate => _set_gate(&idt[n],15,0,addr), system.h
	set_trap_gate(0,&divide_error);
	set_trap_gate(1,&debug);
	set_trap_gate(2,&nmi);

	// 使用陷阱门，执行过程中中断仍然可以产生
	// 这样用户可以使用INT触发执行, 比如int3是断点指令，可以用于辅助调试，插入软件断点
	// 与trap_gate的区别在于DPL=3.
	set_system_gate(3,&int3);	/* int3-5 can be called from all */
	set_system_gate(4,&overflow);
	set_system_gate(5,&bounds);

	set_trap_gate(6,&invalid_op);
	set_trap_gate(7,&device_not_available);
	set_trap_gate(8,&double_fault);
	set_trap_gate(9,&coprocessor_segment_overrun);
	set_trap_gate(10,&invalid_TSS);
	set_trap_gate(11,&segment_not_present);
	set_trap_gate(12,&stack_segment);
	set_trap_gate(13,&general_protection);
	set_trap_gate(14,&page_fault);
	set_trap_gate(15,&reserved);
	set_trap_gate(16,&coprocessor_error);

	// 设置其余的，其它会由硬件驱动去配置
	for (i=17;i<48;i++)
		set_trap_gate(i,&reserved);

	set_trap_gate(45,&irq13);			// 协处理器中断配置

	// 配置中断屏幕位
	outb_p(inb_p(0x21)&0xfb,0x21);	// 主芯片的IRQ2打开，从而从芯片可以传过去（1 << 2)=0x4要清0
	outb(inb_p(0xA1)&0xdf,0xA1);	// 打开从芯片的IRQ13，对应于(1 << 5)=0x20清零，即开协处理器的中断配置

	// 并口。现在基本上没用的接口，在这个系统中没怎么使用
	// 在system_call.S只是简单的响应了一下，就没有做任何处理
	// 所以这里不需要关系。。。
	set_trap_gate(39,&parallel_interrupt);
}
